﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using uam_fps_game.Gfx;
using Microsoft.Xna.Framework;

namespace uam_fps_game.Scenes
{
    class SceneNode
    {
        private Vector3 mPosition;

        internal Vector3 Position
        {
            get { return mPosition; }
            set { mPosition = value; mTransformsDirty = true; }
        }

        private Vector3 mRotation;

        internal Vector3 Rotation
        {
            get { return mRotation; }
            set { mRotation = value; mTransformsDirty = true; }
        }

        private Vector3 mScale;

        internal Vector3 Scale
        {
            get { return mScale; }
            set { mScale = value; mTransformsDirty = true; }
        }

        private bool mTransformsDirty;

        // matrix with transformation relative to parent node
        protected Matrix mRelativeToParentMatrix;

        // matrix with full world transformation - multiplication of parent's world matrix and node's mRelativeToParentMatrix
        // it is the matrix used to render object
        private Matrix mWorldMatrix;

        public Matrix WorldMatrix
        {
            get { return mWorldMatrix; }
            protected set { mWorldMatrix = value; }
        }

        internal List<SceneNode> ChildNodes { get; private set; }

        protected Scene mScene;

        internal SceneNode(Scene scene)
        {
            mScene = scene;

            Position = new Vector3(0, 0, 0);
            Rotation = new Vector3(0, 0, 0);
            Scale = new Vector3(1, 1, 1);

            ChildNodes = new List<SceneNode>();
            mRelativeToParentMatrix = Matrix.Identity;

            mTransformsDirty = true;
        }



        /// <summary>
        /// This code could be in virtual (and thus overridable) Update method, but the logic for updating children
        /// is not likely to change in classes deriving from SceneNode, so UpdateWithChildren() is public method to be called
        /// by parents, and protected overridable Update() method is called inside it and is place for custom node logic.
        /// </summary>
        /// <param name="deltaTime"></param>
        internal void UpdateWithChildren(float deltaTime, Matrix parentWorldMatrix)
        {
            Update(deltaTime, parentWorldMatrix);

            // update children (and their children (and their children (and ... )))
            for (int i = 0; i < ChildNodes.Count; ++i)
            {
                ChildNodes[i].UpdateWithChildren(deltaTime, mWorldMatrix);
            }
        }

        protected virtual void Update(float deltaTime, Matrix parentWorldMatrix)
        {
            UpdateRelativeToParentMatrix();
            Matrix.Multiply(ref parentWorldMatrix, ref mRelativeToParentMatrix, out mWorldMatrix);
        }


        protected void UpdateRelativeToParentMatrix()
        {
            if (!mTransformsDirty)
                return;

            mTransformsDirty = false;

            Matrix scale;
            Matrix.CreateScale(ref mScale, out scale);

            Matrix rotation;
            Matrix.CreateFromYawPitchRoll(mRotation.Y, mRotation.X, mRotation.Z, out rotation);

            Matrix translation;
            Matrix.CreateTranslation(ref mPosition, out translation);

            Matrix.Multiply(ref scale, ref rotation, out mRelativeToParentMatrix);
            Matrix.Multiply(ref mRelativeToParentMatrix, ref translation, out mRelativeToParentMatrix);
        }
    }
}
